package biolabs.mcl

import scala.collection.{Map => AMap}

import biolabs.core.Application
import biolabs.mcl.syntax._

object ContainerSpec {

	def default(forType: MultiType, associatedTypes: List[MultiType]): ContainerSpec =
		associatedTypes match {
			case Nil => default(forType)
			case list => defaultAssociative(forType, list)
		}

	def default(forType: MultiType): SimpleContainerSpec =
		new MutableSeqContainerSpec(forType, Nil)

	private def defaultAssociative(forType: MultiType, associatedTypes: List[MultiType]) = {
		val assocType = associatedTypesAsTuple(associatedTypes)
		new MutableMapContainerSpec(forType, assocType, Nil)
	}

	private def associatedTypesAsTuple(associatedTypes: List[MultiType]): MultiType =
		associatedTypes.length match {
			case 0 => throw new AssertionError("unreachable code")
			case 1 => associatedTypes.head
			case 2 => MultiType(ActualType(classOf[Tuple2[_,_]], associatedTypes))
			case 3 => MultiType(ActualType(classOf[Tuple3[_,_,_]], associatedTypes))
			case 4 => MultiType(ActualType(classOf[Tuple4[_,_,_,_]], associatedTypes))
			case 5 => MultiType(ActualType(classOf[Tuple5[_,_,_,_,_]], associatedTypes))
			case 6 => MultiType(ActualType(classOf[Tuple6[_,_,_,_,_,_]], associatedTypes))
			case 7 => MultiType(ActualType(classOf[Tuple7[_,_,_,_,_,_,_]], associatedTypes))
			case 8 => MultiType(ActualType(classOf[Tuple8[_,_,_,_,_,_,_,_]], associatedTypes))
			case 9 => MultiType(ActualType(classOf[Tuple9[_,_,_,_,_,_,_,_,_]], associatedTypes))
			case _ => throw new IllegalArgumentException("A maximum number of 9 associated values was exceeded")
		}

	def analyze(ast: ContainerSpecAST, forType: MultiType, associatedTypes: List[MultiType]): AnalysisResult[ContainerSpec] = {
		// get the container class
		val cls = try {
			Class.forName(ast.className)
		} catch {
			case e: ClassNotFoundException =>
				return AnalysisError("Class " + ast.className + " not found. Make sure that\n" +
						"  1) you provided a fully qualified name, and\n" +
						"  2) the class was not defined in the interpreter.")
		}

		associatedTypes match {

			// simple (non-associative) container
			case Nil =>
				if(classOf[scala.collection.mutable.Seq[_]].isAssignableFrom(cls))
					AnalysisSuccess(new MutableSeqContainerSpec(cls, forType, ast.initArgs))
				else if(classOf[scala.collection.immutable.Seq[_]].isAssignableFrom(cls))
					AnalysisSuccess(new ImmutableSeqContainerSpec(cls, forType, ast.initArgs))
				else
					AnalysisError("Only descendants of scala.collection.Seq are supported as non-associative container types. Provided: " + cls.getName)

			// associative container
			case list =>
				val assocType = associatedTypesAsTuple(associatedTypes)
				if(classOf[scala.collection.mutable.Map[_,_]].isAssignableFrom(cls))
					AnalysisSuccess(new MutableMapContainerSpec(cls, forType, assocType, ast.initArgs))
				else if(classOf[scala.collection.immutable.Map[_,_]].isAssignableFrom(cls))
					AnalysisSuccess(new ImmutableMapContainerSpec(cls, forType, assocType, ast.initArgs))
				else
					AnalysisError("Only descendants of scala.collection.Map are supported as associative container types. Provided: " + cls.getName)
		}
	}
}

abstract class ContainerSpec {
	val cls: Class[_]
	val childType: MultiType
	val initArgs: List[String]
	val isAssociative: Boolean
}

abstract class SimpleContainerSpec extends ContainerSpec {
	override val isAssociative = false
}

abstract class AssociativeContainerSpec extends ContainerSpec {
	val associatedType: MultiType
	override val isAssociative = true
}

class MutableSeqContainerSpec(
		val cls: Class[_],
		val childType: MultiType,
		val initArgs: List[String])
extends SimpleContainerSpec {
	def this(childType: MultiType, initArgs: List[String]) =
		this(classOf[scala.collection.mutable.Seq[_]], childType, initArgs)
}

class ImmutableSeqContainerSpec(
		val cls: Class[_],
		val childType: MultiType,
		val initArgs: List[String])
extends SimpleContainerSpec

class MutableMapContainerSpec(
		val cls: Class[_],
		val childType: MultiType,
		val associatedType: MultiType,
		val initArgs: List[String])
extends AssociativeContainerSpec {
	def this(childType: MultiType, associatedType: MultiType, initArgs: List[String]) =
		this(classOf[scala.collection.mutable.Map[_, _]], childType, associatedType, initArgs)
}

class ImmutableMapContainerSpec(
		val cls: Class[_],
		val childType: MultiType,
		val associatedType: MultiType,
		val initArgs: List[String])
extends AssociativeContainerSpec